(*
  Copyright (c) 2009 Barry Schwartz

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation
  files (the "Software"), to deal in the Software without
  restriction, including without limitation the rights to use,
  copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following
  conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.
*)

open Pycaml
open Unix
open XNum

let py_null = pynull ()

let py_throw exc =
  pyerr_clear ();
  raise exc

let py_fail s =
  pyerr_clear ();
  failwith s

let argument_count_error desired_arg_count actual_arg_count =
  Pycaml_exn(Pyerr_IndexError,
			 (Printf.sprintf "Args given: %d Wanted: %d" actual_arg_count desired_arg_count))

let check_argument_count desired_arg_count arg_array =
  let actual_arg_count = Array.length arg_array in
    if actual_arg_count <> desired_arg_count then
      raise (argument_count_error desired_arg_count actual_arg_count)

let py_module name =
  let m = pyimport_importmodule name in
    if m = py_null then
      py_fail ("Failed to import Python module: " ^ name)
    else
      m

let py_module_call m function_name args =
  let dict = pymodule_getdict m in
  let func = pydict_getitemstring (dict, function_name) in
    pyeval_callobject (func, args)

let py_module_call_with_keywords m function_name args keyword_args =
  let dict = pymodule_getdict m in
  let func = pydict_getitemstring (dict, function_name) in
    pyeval_callobjectwithkeywords (func, args, keyword_args)

let py_object_call obj method_name args =
  let func = pyobject_getattrstring (obj, method_name) in
    pyeval_callobject (func, args)

let py_object_call_with_keywords obj method_name args keyword_args =
  let func = pyobject_getattrstring (obj, method_name) in
    pyeval_callobjectwithkeywords (func, args, keyword_args)

let py_builtins_call function_name args =
  let dict = pyeval_getbuiltins () in
  let func = pydict_getitemstring (dict, function_name) in
    pyeval_callobject (func, args)

let py_builtins_call_with_keywords function_name args keyword_args =
  let dict = pyeval_getbuiltins () in
  let func = pydict_getitemstring (dict, function_name) in
    pyeval_callobjectwithkeywords (func, args, keyword_args)

let pydict_of_association_list alist =
  let dict = pydict_new () in
  let insert (key, value) = 
    let _ = pydict_setitem (dict, key, value) in
      ()
  in
    List.iter insert alist;
    dict

let pydict_of_str_association_list alist =
  pydict_of_association_list (List.map (fun (k, v) -> (pythonize_string k, v)) alist)

let pydict_to_association_list py_d =
  let items = Array.to_list (pylist_toarray (pydict_items py_d)) in
  let convert_tuple t =
    let a = pytuple_toarray t in
      (a.(0), a.(1))
  in
    List.map convert_tuple items
  
let pythonize_bool b =
  if b then
    py_true ()
  else
    py_false ()

let unpythonize_bool b =
  py_is_true b

let pythonize_option pythonizer x =
  match x with
    | None   -> pynone ()
    | Some u -> pythonizer u

let unpythonize_option unpythonizer x =
  if pytype x = NoneType then
    None
  else
    Some (unpythonizer x)

let pythonize_array pythonizer x =
  pylist_fromarray (Array.map pythonizer x)

let unpythonize_array unpythonizer x =
  Array.map unpythonizer (pylist_toarray x)

let pythonize_list pythonizer x =
  pylist_fromarray (Array.of_list (List.map pythonizer x))

let unpythonize_list unpythonizer x =
  List.map unpythonizer (Array.to_list (pylist_toarray x))

let pythonize_tm t =
  let sequence =
    pylist_fromarray
      [| pyint_fromint (1900 + t.tm_year);
         pyint_fromint (1 + t.tm_mon);
         pyint_fromint t.tm_mday;
         pyint_fromint t.tm_hour;
         pyint_fromint t.tm_min;
         pyint_fromint t.tm_sec;
         pyint_fromint t.tm_wday;
         pyint_fromint t.tm_yday;
         pyint_fromint (if t.tm_isdst then 1 else 0) |]
  in
  let time = py_module "time" in
    py_module_call time "struct_time" (pytuple_fromsingle sequence)

let unpythonize_tm py_time =
  let sequence = pylist_toarray (pysequence_list py_time) in
  let values = Array.map pyint_asint sequence in
    { tm_year  = values.(0) - 1900;
      tm_mon   = values.(1) - 1;
      tm_mday  = values.(2);
      tm_hour  = values.(3);
      tm_min  = values.(4);
      tm_sec  = values.(5);
      tm_wday  = values.(6);
      tm_yday  = values.(7);
      tm_isdst = (values.(8) == 1) }

let unpythonize_int_tuple =
  unpythonizing_function
    ~expect_tuple:true
    [| IntType; IntType |]
    (fun py_args ->
       (pyint_asint py_args.(0),
        pyint_asint py_args.(1)))

let unpythonize_string_tuple =
  unpythonizing_function
    ~expect_tuple:true
    [| EitherStringType; EitherStringType |]
    (fun py_args ->
       (unpythonize_string py_args.(0),
        unpythonize_string py_args.(1)))

let pywrap_int_to_int_function funct =
  python_interfaced_function
    [| IntType |]
    (fun py_args ->
       pyint_fromint (funct (pyint_asint py_args.(0))))
